
const net = require('net');
const fft = require('fft-js').fft;
const fftUtil = require('fft-js').util;


const CHART_WIDTH = 800;
const CHART_HEIGHT = 400;

// 获取 canvas 元素
const canvasChartTD = document.getElementById('ChartTD').getContext('2d');
const canvasChartFD = document.getElementById('ChartFD').getContext('2d');
const labelStatus = document.getElementById('Status');
const labelSampleRate = document.getElementById('SampleRate');

const buttonZoomInChartTD = document.getElementById('ZoomInChartTD');
const buttonZoomOutChartTD = document.getElementById('ZoomOutChartTD');
const buttonZoomInChartFD = document.getElementById('ZoomInChartFD');
const buttonZoomOutChartFD = document.getElementById('ZoomOutChartFD');

  

let unique_socket_client = null;
let s16pcmBuffer = null;
let pcmSampleRate = 16000;




// 创建图表
const ChartTimeDomain = new Chart(canvasChartTD, {

  type: 'bar',
  data: {
    labels: [],
    datasets: [{
      label: 'Time-Domain / Amplitude',
      data: [],
      backgroundColor: '#0000FF',
    }]
  },
  options: {

    /// fixed the size
    responsive: false,
    width: CHART_WIDTH,
    height: CHART_HEIGHT,

    scales: {
      x: {
        ticks: {
          beginAtZero: true,
          // autoSkip: false,
          // stepSize: 1,
          // display: false,
          // maxRotation: 45,
          // minRotation: 45,

          /// hidden axis
          color: 'rgba(255,255,255,0)',
        },
        grid: {
          display: false
        }
      },
      y: {
        beginAtZero: false,

        /// for s16pcm
        /// minimum = -65535 / 2
        /// maxmum  =  65535 / 2
        min: -40000,
        max: 40000,

      },
    },

    
  },

});


// // 为按钮添加点击事件监听器
buttonZoomInChartTD.addEventListener('click', function () {

  let range = ChartTimeDomain.options.scales.y.max;
  let step = 10000;

  if (range > 1000) {

    if (range > 10000) {
      step = 10000;
    } else {
      step = 1000;
    }

    range -= step;
    range = Math.round(range / step) * step;

    ChartTimeDomain.options.scales.y.max = range;
    ChartTimeDomain.options.scales.y.min = -range;
    ChartTimeDomain.update();
  }
});

buttonZoomOutChartTD.addEventListener('click', function () {

  let range = ChartTimeDomain.options.scales.y.max;

  if (range < 40000) {

    range += 10000;
    range = Math.round(range / 10000) * 10000;

    ChartTimeDomain.options.scales.y.max = range;
    ChartTimeDomain.options.scales.y.min = -range;
    ChartTimeDomain.update();
  }
});


// 创建图表
const ChartFrequencyDomain = new Chart(canvasChartFD, {

  type: 'bar',
  data: {
    labels: [],
    datasets: [{
      label: 'Frequency-Domain / Spectrum',
      data: [],
      backgroundColor: '#007FFF',
    }]
  },
  options: {

    /// fixed the size
    responsive: false,
    width: CHART_WIDTH,
    height: CHART_HEIGHT,

    scales: {
      x: {
        ticks: {
          beginAtZero: true,
          // autoSkip: false,
          // stepSize: 500,
          // display: false,
          // maxRotation: 45,
          // minRotation: 45,

          /// hidden axis
          // color: 'rgba(255,255,255,0)',
        },
        grid: {
          display: false
        }
      },
      y: {
        beginAtZero: false,

        /// for s16pcm
        /// minimum = -65535 / 2
        /// maxmum  =  65535 / 2
        min: 0,
        max: 100,

      },
    },

  },

});


// // 为按钮添加点击事件监听器
buttonZoomInChartFD.addEventListener('click', function () {

  const chart = ChartFrequencyDomain;
  let range = chart.options.scales.y.max;
  let step = 100;

  if (range > 100) {

    range -= step;
    range = Math.round(range / step) * step;

    chart.options.scales.y.max = range;
    chart.update();
  }
});

buttonZoomOutChartFD.addEventListener('click', function () {

  const chart = ChartFrequencyDomain;
  let range = chart.options.scales.y.max;

  if (range < 1000) {

    range += 100;
    range = Math.round(range / 100) * 100;

    chart.options.scales.y.max = range;
    chart.update();
  }
});


function updateChart(which, xAxis, yAxis)
{
  which.data.labels = xAxis;
  which.data.datasets[0].data = yAxis;
  which.update({duration: 0});
}



function update_chart(int16pcmArray)
{

  let length = int16pcmArray.length;
  let xAxis = Array.from({ length }, (_, index) => (0));
  let yAxis = int16pcmArray;

  /// update chart of time-domain
  updateChart(ChartTimeDomain, xAxis, yAxis);


  const inputFFT = Array.from(int16pcmArray).map((value) => value / (65536 / 2));
  let fftResult = fft(inputFFT);
  let spectrum = [];

  for (let i = 0; i < fftResult.length / 2; i++) {
    
    const re = fftResult[i][0];
    const im = fftResult[i][1];
    const magnitude = Math.sqrt(re * re + im * im);

    spectrum.push(magnitude);
  }

  length = spectrum.length;
  xAxis = Array.from({ length }, (_, index) => parseInt(index * pcmSampleRate / int16pcmArray.length));
  yAxis = spectrum;

  updateChart(ChartFrequencyDomain, xAxis, yAxis);

}




function socket_onRecv(data)
{
  // console.log(`Received data: ${data}`);
  if (null == s16pcmBuffer) {

    s16pcmBuffer = new Int16Array(data.buffer);

  } else {

    let fft_points = parseInt(pcmSampleRate * 1024 / 1000);

    /// half second
    // fft_points /= 8;
    fft_points = 1024;

    appendArray = new Int16Array(data.buffer);
    mergeArray = new Int16Array(s16pcmBuffer.length + appendArray.length);
    mergeArray.set(s16pcmBuffer);
    mergeArray.set(appendArray, s16pcmBuffer.length);
    s16pcmBuffer = mergeArray;

    if (s16pcmBuffer.length >= fft_points) {


      const computeArray = s16pcmBuffer.subarray(0, fft_points);
      const minValue = computeArray.reduce((min, current) => Math.min(min, current), Infinity);
      const maxValue = computeArray.reduce((max, current) => Math.max(max, current), -Infinity);

      /// 性能不够，只能在音压很低的时候不画图。
      if (minValue < -100 || maxValue > 100) {
        update_chart(computeArray);
      }

      s16pcmBuffer = s16pcmBuffer.subarray(fft_points);
    }
  }
}

function socket_onClose()
{
  /// clear the unique flag
  unique_socket_client = null;
  /// empty the pcm buffer
  s16pcmBuffer = null;
  labelStatus.textContent = 'Disconnected';
  labelSampleRate.textContent = '';
  document.title = 'Frank is having a coffee nap☕️'
  console.log('Connection closed');
}

function socket_onError(err)
{
  /// clear the unique flag
  socket_onClose();
  console.log('Connection error' + err);
}


function socketSetup(socket, sampleRate)
{
  unique_socket_client = socket;
  pcmSampleRate = sampleRate;
  address = socket.remoteAddress.split(':').pop() + ':' + socket.remotePort;
  labelStatus.textContent = 'Connected by Terminal(' + address + ')';
  labelSampleRate.textContent = 'Sampling ' + sampleRate + '/s';
  document.title = 'Frank is working with terminal(' + address + ')🎧️';
  
  socket.on('data', socket_onRecv);
  socket.on('end', socket_onClose);
  socket.on('error', socket_onError);
}

const server = net.createServer((socket) => {

  if (null != unique_socket_client) {
    console.log('refuse connection');
    socket.destroy();
    return;
  }
  
  socketSetup(socket, 8000);

});

const server2 = net.createServer((socket) => {

  if (null != unique_socket_client) {
    console.log('refuse connection');
    socket.destroy();
    return;
  }
  
  socketSetup(socket, 16000);

});

/// explict close one time to change the status
socket_onClose();

/// for 8k sample rate input
server.listen(60008, () => {
});

/// for 16k sample rate input
server2.listen(60016, () => {
});

